package org.xtext.runtime.plan;

import is.lill.acre.conversation.Conversation;
import is.lill.acre.message.ACREAgentIdentifier;
import is.lill.acre.message.ACREMessage;
import is.lill.acre.protocol.Protocol;
import is.lill.acre.protocol.ProtocolDescriptor;
import is.lill.acre.protocol.ProtocolVersion;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.xtext.runtime.acre.ACREService;
import org.xtext.runtime.formula.Predicate;
import org.xtext.runtime.interpreter.Agent;
import org.xtext.runtime.messaging.AstraMessage;
import org.xtext.runtime.term.ITerm;
import org.xtext.runtime.term.Term;
import org.xtext.runtime.term.Variable;
import org.xtext.runtime.type.TypeException;
import org.xtext.runtime.util.Bindings;
import org.xtext.runtime.util.EvaluateTermsVisitor;

/**
 * Implements the plan step for invoking an EIS action. 
 * @author rem
 *
 */
public class AcreStartConversationStep implements IPlanStep {
	private ITerm protocol;
	private ITerm receiver;
	private String performative;
	private Predicate content;
	private Variable cid;
	
	public AcreStartConversationStep(String performative, ITerm protocol, ITerm receiver, Predicate content, Variable cid) {
		this.performative = performative;
		this.protocol = protocol;
		this.receiver = receiver;
		this.content = content;
		this.cid = cid;
	}
	
	@Override
	public AcreStartConversationStep apply(Bindings set) {
		protocol = protocol.apply(set);
		receiver = receiver.apply(set);
		content = content.apply(set);
		return this;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void handle(Agent agent, PlanStack planStack) {
		EvaluateTermsVisitor visitor = new EvaluateTermsVisitor(agent);
		Term<String> p = (Term<String>) protocol.accept(visitor);
		Term<String> r = (Term<String>) receiver.accept(visitor);
		Predicate c = (Predicate) content.accept(visitor);
		
		ProtocolDescriptor pd = ProtocolDescriptor.parseString(p.value());
		Protocol protocol = matchProtocol(agent, pd);
		if (protocol != null) {
			ACREMessage m = new ACREMessage();
			m.setPerformative(performative);
			m.setReceiver(new ACREAgentIdentifier(r.value().toString()));
			m.setSender(agent.getAcreIdentifier());
			m.setContent(c.toString());
			m.setConversationIdentifier(agent.getConversationManager().getNextConversationId());
			m.setProtocol(protocol.getDescriptor());
			m.setLanguage("ASTRA");
			AstraMessage message = ACREService.messageWithContent(m,c);
			
			Conversation con = agent.getConversationManager().processMessage(m);
			
			// in ASTRA, message sending can't fail
			// TODO: Change if non-local messaging is implemented
			if (agent.messageService().send(message)) {
				
				try {
					cid.assign(Term.newInstance(con.getConversationIdentifier()));
				} catch (TypeException e) {
					e.printStackTrace();
					planStack.fail();
				}
			} else {
				planStack.fail();
			}
		} else {
			planStack.fail();
		}
		

	}

	@Override
	public String toString(String tabs) {
		return tabs + "acre_start(" + protocol + "," + receiver + "," + performative + "," + content + ")";
	}
	
	public String toFullString(String tabs) {
		return toString(tabs);
	}
	
	@Override
	public Set<Variable> variables() {
		Set<Variable> vars = new HashSet<Variable>();
		if (protocol instanceof Variable) vars.add((Variable) protocol);
		if (receiver instanceof Variable) vars.add((Variable) receiver);
		vars.addAll(content.variables());
		if (cid instanceof Variable) vars.add((Variable) cid);
		return vars;
	}

	@Override
	public IPlanStep copy(Map<String, Variable> vars) {
		return new AcreStartConversationStep(performative, protocol.copy(vars), receiver.copy(vars), content.copy(vars), cid.copy(vars));
	}

	public String toString() {
		return toString("");
	}

	@Override
	public void reset() {
	}

	private Protocol matchProtocol( Agent agent, ProtocolDescriptor pd ) {

        Protocol toReturn = null;

        // find how many protocols match the descriptor
        Set<Protocol> matches = ACREService.manager.getProtocolsMatchingDescriptor( pd );

        // no matching protocols
        if ( matches.isEmpty() ) {
            agent.addBelief( new Predicate("unknownProtocol", new ITerm[] { Term.newInstance(pd.toString()) } ));
        }
        else if ( matches.size() == 1 ) {
            for ( Protocol p : matches ) {
                toReturn = p;
            }
            agent.addBelief( new Predicate("protocolMatch",
            		new ITerm[] {
            			Term.newInstance(pd.toString()),
            			Term.newInstance(toReturn.getDescriptor().getUniqueID())            			
            }));
        }

        // multiple matches but name and namespace must match
        // pick the latest version number
        else if ( pd.getNamespace() != null ) {
            ProtocolVersion maxVersion = null;
            for ( Protocol p : matches ) {
                if ( p.getDescriptor().getVersion().isLaterThan( maxVersion ) ) {
                    toReturn = p;
                    maxVersion = p.getDescriptor().getVersion();
                }
            }
        }

        // multiple matches from different namespaces:
        // ambiguous message
        else {
            agent.addBelief( new Predicate("ambiguousProtocol", new ITerm[] { Term.newInstance(pd.toString()) } ));
        }
        
        return toReturn;
    }}
